import sys
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                          QHBoxLayout, QPushButton, QLabel, QFrame,
                          QFileDialog, QMessageBox, QLineEdit, QTextEdit, QSizePolicy,
                          QGridLayout, QScrollArea, QStyle)
from PyQt6.QtCore import Qt, QPropertyAnimation, QEasingCurve, QSize, QTimer
from PyQt6.QtGui import QPalette, QColor, QIcon
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import FastICA
from scipy.stats import gaussian_kde
import seaborn as sns
import warnings
from window_state import manager

# 忽略来自mpld3的UserWarning
warnings.filterwarnings("ignore", category=UserWarning, module="mpld3")

# 设置全局字体为 Microsoft YaHei（确保已安装）
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False  # 正确显示负号

def get_reconstruction_error(ica, X_scaled):
    """计算重构误差"""
    try:
        # 使用ICA模型重构数据
        S = ica.transform(X_scaled)
        X_reconstructed = ica.inverse_transform(S)
        
        # 计算均方误差
        reconstruction_error = np.mean((X_scaled - X_reconstructed) ** 2)
        return reconstruction_error
    except Exception as e:
        print(f"计算重构误差时出错: {str(e)}")
        return float('inf')

class ICAAnalyzer:
    def __init__(self):
        self.ica = None
        self.scaler = None
        self.train_data = None
        self.test_data = None
        self.X_train_scaled = None
        self.X_test_scaled = None
        self.S_train = None
        self.S_test = None
        self.I2_train = None
        self.I2_test = None
        self.control_limit_kde = None
        self.anomalies = None
        self.components = None
        self.columns = None
        self.chinese_columns = None  # 添加中文列名属性

    def preprocess_data(self, data_df):
        """优化后的数据预处理方法"""
        try:
            # 1. 检查数据是否为空
            if data_df is None or data_df.empty:
                raise ValueError("输入数据为空")
            
            # 2. 检查数据类型
            if not isinstance(data_df, pd.DataFrame):
                data_df = pd.DataFrame(data_df)
            
            # 3. 检测并处理恒定值列
            constant_cols = []
            for col in data_df.columns:
                # 更严格的恒定值检测
                unique_vals = data_df[col].dropna().unique()
                if len(unique_vals) == 1:  # 只有唯一值
                    constant_cols.append(col)
                elif len(unique_vals) < 10:  # 值种类过少
                    # 检查数值变化是否在容差范围内
                    if np.std(data_df[col]) < 1e-6:
                        constant_cols.append(col)
                
            # 处理恒定值列
            if constant_cols:
                print(f"检测到 {len(constant_cols)} 个恒定值列，将被移除: {constant_cols}")
                data_df = data_df.drop(columns=constant_cols)
            
            # 4. 检查剩余列数
            if len(data_df.columns) < 2:
                raise ValueError("移除恒定值列后，有效变量数不足2个")
            
            # 5. 数值类型转换和缺失值处理
            valid_columns = []
            for col in data_df.columns:
                try:
                    # 尝试转换为数值类型
                    if not np.issubdtype(data_df[col].dtype, np.number):
                        data_df[col] = pd.to_numeric(data_df[col], errors='coerce')
                    
                    # 检查是否有NaN值并填充
                    if data_df[col].isnull().any():
                        col_mean = data_df[col].mean()
                        if np.isnan(col_mean):  # 如果整列都是NaN
                            print(f"列 {col} 全为无效值，将被移除")
                            continue
                        print(f"列 {col} 的缺失值使用均值 {col_mean:.2f} 填充")
                        data_df[col] = data_df[col].fillna(col_mean)
                    
                    # 检查填充后是否还有NaN
                    if not data_df[col].isnull().any():
                        valid_columns.append(col)
                    else:
                        print(f"列 {col} 仍包含无效值，将被移除")
                except Exception as e:
                    print(f"列 {col} 处理失败: {str(e)}")
                    continue

            # 只保留有效的列
            invalid_columns = set(data_df.columns) - set(valid_columns)
            if invalid_columns:
                print(f"以下列由于无效值已被移除: {list(invalid_columns)}")
            data_df = data_df[valid_columns]
            
            # 7. 检查数据范围
            for col in data_df.columns:
                col_std = data_df[col].std()
                if col_std < 1e-6:  # 标准差过小
                    print(f"警告：列 {col} 的标准差过小 ({col_std:.2e})")
                    data_df = data_df.drop(columns=[col])
            
            # 8. 最终检查
            if len(data_df.columns) < 2:
                raise ValueError("预处理后有效变量数不足2个")
            
            # 9. 标准化数据
            scaler = StandardScaler()
            scaled_data = scaler.fit_transform(data_df)
            
            return pd.DataFrame(scaled_data, columns=data_df.columns)
            
        except Exception as e:
            print(f"数据预处理失败: {str(e)}")
            import traceback
            traceback.print_exc()
            return None

    def plot_contribution_for_anomalies(self, anomaly_indices):
        """为每个异常点绘制贡献图"""
        figures = []
        
        # 设置matplotlib的全局参数
        plt.rcParams['toolbar'] = 'None'  # 禁用工具栏
        
        for idx in anomaly_indices:
            # 计算贡献度
            S_anomaly = self.S_train[idx].reshape(1, -1)
            contribution = (S_anomaly ** 2) @ (self.ica.components_ ** 2)
            contribution = contribution.flatten()
            
            # 获取变量名
            column_names = self.chinese_columns if self.chinese_columns is not None else self.train_data.columns
            
            # 创建每个异常点的贡献图之前清理之前的图形
            plt.close('all')
            # 创建图形时禁用工具栏
            fig = plt.figure(figsize=(12, 8), frameon=False)  # 添加 frameon=False
            ax = fig.add_subplot(111)
            
            # 禁用图形的交互功能
            ax.set_frame_on(True)  # 保持边框
            for item in ([ax.title, ax.xaxis.label, ax.yaxis.label] +
                        ax.get_xticklabels() + ax.get_yticklabels()):
                item.set_fontsize(8)  # 设置字体大小
            
            # 绘制柱状图
            sorted_idx = np.argsort(contribution)[::-1]
            sorted_contrib = contribution[sorted_idx]
            sorted_names = [column_names[i] for i in sorted_idx]
            
            # 只显示前10个主要贡献变量
            top_n = min(10, len(sorted_contrib))
            bars = ax.bar(range(top_n), sorted_contrib[:top_n], color='skyblue')
            
            # 修改这里：将数值标签格式化为整数
            for bar in bars:
                height = bar.get_height()
                ax.text(bar.get_x() + bar.get_width()/2., height,
                        f'{int(height)}',  # 将浮点数转换为整数
                        ha='center', va='bottom')
            
            # 设置图形属性
            ax.set_xticks(range(top_n))
            ax.set_xticklabels(sorted_names[:top_n], rotation=45, ha='right', rotation_mode='anchor')
            ax.set_ylabel('贡献度')
            ax.set_title(f'异常点 #{idx + 1} 的主要贡献变量')
            
            # 调整布局以确保标签完全可见
            plt.subplots_adjust(bottom=0.2)  # 增加底部空间
            plt.tight_layout()
            
            figures.append(fig)
        
        return figures

    def main_function(self, data_df, n_components=None):
        """主分析函数"""
        try:
            # 预处理数据
            processed_data = self.preprocess_data(data_df)
            if processed_data is None:
                return None

            # 检查数据维度
            if processed_data.shape[1] < 2:
                raise ValueError("数据维度过低，至少需要2个变量")

            # 不再划分数据集和数据集
            self.train_data = processed_data
            self.test_data = processed_data  # 保持相同引用
            
            # 标准化处理
            self.scaler = StandardScaler()
            self.X_train_scaled = self.scaler.fit_transform(self.train_data)
            self.X_test_scaled = self.X_train_scaled  # 使用相同数据
            
            # ICA分析
            if n_components is None:
                n_components = min(4, self.train_data.shape[1])
                
            self.ica = FastICA(n_components=n_components, random_state=42, max_iter=1000)
            self.S_train = self.ica.fit_transform(self.X_train_scaled)
            self.S_test = self.S_train  # 使用相同数据
            
            # 计算I²统计量
            self.I2_train = np.sum(self.S_train**2, axis=1)
            self.I2_test = self.I2_train  # 使用相同数据
            
            # 使用KDE计算控制限
            confidence_level = 99  # 修改为99% (原来是95%)
            kde = gaussian_kde(self.I2_train)
            I2_min, I2_max = self.I2_train.min(), self.I2_train.max()
            I2_grid = np.linspace(I2_min, I2_max, 1000)
            kde_values = kde.evaluate(I2_grid)
            cdf = np.cumsum(kde_values)
            cdf /= cdf[-1]  # 归一化为0到1
            
            # 确定控制限：找到CDF >= 99% 的I²值
            self.control_limit_kde = I2_grid[np.searchsorted(cdf, confidence_level / 100)]
            print(f"基于KDE的{confidence_level}%控制限: {self.control_limit_kde:.2f}")
            
            # 验证控制限计算
            actual_percent_kde = np.sum(self.I2_train <= self.control_limit_kde) / len(self.I2_train) * 100
            print(f"实际数据中I² <= {self.control_limit_kde:.2f}的百分比: {actual_percent_kde:.2f}%")
            
            # 4. 故障检测 (数据集)
            anomaly_indices_train = np.where(self.I2_train > self.control_limit_kde)[0]
            anomaly_values_train = self.I2_train[anomaly_indices_train]
            print(f"数据集中检测到 {len(anomaly_indices_train)} 个异常点")

            # 关闭之前的图形并绘制新的I²监控图
            plt.close('all')
            fig_train = plt.figure(figsize=(12, 7), dpi=100)
            ax = fig_train.add_subplot(111)
            
            # 设置合理的y轴范围
            y_min = min(self.I2_train) * 0.9
            y_max = max(self.I2_train) * 1.1
            if self.control_limit_kde > y_max:
                y_max = self.control_limit_kde * 1.1
            
            # 绘制主图
            ax.plot(self.I2_train, label='I²统计量', color='blue', linewidth=1.5)
            ax.axhline(self.control_limit_kde, color='red', linestyle='--', 
                      label=f'控制限 ({self.control_limit_kde:.2f})', linewidth=1.5)
            ax.scatter(anomaly_indices_train, self.I2_train[anomaly_indices_train], 
                      color='orange', label=f'异常点 ({len(anomaly_indices_train)}个)', 
                      zorder=5, s=50)
            
            # 设置图形属性
            ax.set_ylim([y_min, y_max])
            ax.set_title('数据集I²统计量监控', fontsize=14)
            ax.set_xlabel('样本编号', fontsize=12)
            ax.set_ylabel('I²统计量', fontsize=12)
            ax.legend(fontsize=12, loc='upper left')
            ax.grid(True, linestyle='--', alpha=0.7)
            
            # 调整布局
            plt.tight_layout(pad=2.0)
            fig1 = fig_train
            print("数据集I²监控图已添加到figures")

            # 5. 故障检测 (数据集)
            S_test = self.ica.transform(self.X_test_scaled)
            self.I2_test = np.sum(S_test**2, axis=1)
            self.anomalies = self.I2_test > self.control_limit_kde
            anomaly_indices_test = np.where(self.anomalies)[0]
            anomaly_values_test = self.I2_test[anomaly_indices_test]
            print(f"数据集中检测到 {len(anomaly_indices_test)} 个异常点")

            # 绘制数据集I²监控图
            fig_test = plt.figure(figsize=(12, 7), dpi=100)  # 与数据集一致
            ax_test = fig_test.add_subplot(111)
            
            # 设置合理的y轴范围
            y_min_test = min(self.I2_test) * 0.9
            y_max_test = max(self.I2_test) * 1.1
            if self.control_limit_kde > y_max_test:
                y_max_test = self.control_limit_kde * 1.1
            
            # 绘制主图
            ax_test.plot(self.I2_test, label='I²统计量', color='blue', linewidth=1.5)
            ax_test.axhline(self.control_limit_kde, color='red', linestyle='--', 
                          label=f'控制限 ({self.control_limit_kde:.2f})', linewidth=1.5)
            ax_test.scatter(anomaly_indices_test, self.I2_test[anomaly_indices_test], 
                          color='orange', label=f'异常点 ({len(anomaly_indices_test)}个)', 
                          zorder=5, s=50)
            
            # 设置图形属性
            ax_test.set_ylim([y_min_test, y_max_test])
            ax_test.set_title('数据集I²统计量监控', fontsize=14)
            ax_test.set_xlabel('样本编号', fontsize=12)
            ax_test.set_ylabel('I²统计量', fontsize=12)
            ax_test.legend(fontsize=12, loc='upper left')
            ax_test.grid(True, linestyle='--', alpha=0.7)
            
            # 调整布局
            plt.tight_layout(pad=2.0)
            fig2 = fig_test
            print("数据集I²监控图已添加到figures")

            # 6. 贡献图
            self.columns = self.train_data.columns  # 使用数据集的列名
            if len(anomaly_indices_test) > 0:
                S_anomaly = S_test[anomaly_indices_test]
                contribution = (S_anomaly ** 2) @ (self.ica.components_ ** 2)
                contribution_mean = np.mean(contribution, axis=0)

                fig_contribution = plt.figure(figsize=(12, 6))
                sns.barplot(x=self.columns, y=contribution_mean)
                plt.xticks(rotation=45)
                plt.xlabel('变量', fontsize=12)
                plt.ylabel('贡献值', fontsize=12)
                plt.title('异常样本的变量贡献度', fontsize=14)
                plt.tight_layout()
                fig_contribution = fig_contribution
                print("异常样本的变量贡献图已添加到figures")
            else:
                # 如果没有异常点，创建一个空的贡献图
                fig_contribution = plt.figure(figsize=(12, 6))
                plt.text(0.5, 0.5, '没有异常点，无法显示贡献图', horizontalalignment='center', verticalalignment='center', fontsize=14)
                plt.axis('off')
                fig_contribution = fig_contribution
                print("没有异常点，空贡献图已添加到figures")

            # 创建新的贡献图
            plt.close('all')  # 关闭之前的所有图形
            fig3 = plt.figure(figsize=(12, 8))
            
            # 按贡献度排序
            sorted_idx = np.argsort(contribution_mean)[::-1]
            sorted_contributions = np.array(contribution_mean)[sorted_idx]
            sorted_labels = np.array(self.columns)[sorted_idx]
            
            # 处理长变量名
            max_label_length = 30  # 最大显示长度
            truncated_labels = [label[:max_label_length] + '...' if len(label) > max_label_length 
                              else label for label in sorted_labels]
            
            # 只显示前20个最重要的变量
            top_n = 20
            if len(sorted_contributions) > top_n:
                sorted_contributions = sorted_contributions[:top_n]
                truncated_labels = truncated_labels[:top_n]
                plt.title(f'数据集变量贡献度排名（前{top_n}个）', fontsize=14)
            else:
                plt.title('数据集变量贡献度排名', fontsize=14)
            
            # 动态调整图形高度
            fig_height = max(6, len(sorted_contributions) * 0.5)
            fig3.set_size_inches(12, fig_height)
            
            # 使用水平条形图
            y_pos = np.arange(len(sorted_contributions))
            bars = plt.barh(y_pos, sorted_contributions, color='skyblue', height=0.6)
            
            # 添加数据标签
            for i, (bar, value) in enumerate(zip(bars, sorted_contributions)):
                plt.text(bar.get_width() * 1.02, i, 
                        f'{value:.2f}', 
                        va='center', fontsize=10)
            
            # 设置y轴标签
            plt.yticks(y_pos, truncated_labels, fontsize=10)
            plt.xlabel('贡献度', fontsize=12)
            plt.ylabel('变量', fontsize=12)
            
            # 添加网格线
            plt.grid(True, axis='x', linestyle='--', alpha=0.7)
            
            # 调整布局
            plt.subplots_adjust(left=0.35)  # 根据最长标签调整左边距
            plt.tight_layout()
            plt.gca().invert_yaxis()  # 最重要的变量在顶部

            # 确保返回正确的图形列表
            figures = [fig1, fig2, fig3]
            if fig_contribution is not None:  # 如果存在贡献图
                figures.append(fig_contribution)
            
            # 在检测到异常点时绘制贡献图
            if len(anomaly_indices_train) > 0:
                contribution_figures = self.plot_contribution_for_anomalies(anomaly_indices_train)
                figures.extend(contribution_figures)
            
            return figures
            
        except Exception as e:
            print(f"生成图表时出错: {str(e)}")
            return None

    def create_dynamic_monitoring_chart(self):
        """创建用于动态绘制的I²监控图"""
        if not hasattr(self, 'I2_train') or self.I2_train is None or not hasattr(self, 'control_limit_kde'):
            print("没有I²统计量数据，无法创建动态监控图")
            return None
        
        # 彻底清理所有matplotlib图形资源
        plt.close('all')  # 关闭之前的所有图形
        
        # 强制垃圾回收以释放资源
        import gc
        gc.collect()
        
        # 创建图形和子图
        fig = plt.figure(figsize=(12, 7), dpi=100)
        ax = fig.add_subplot(111)
        
        # 设置合理的y轴范围
        y_min = min(self.I2_train) * 0.9
        y_max = max(self.I2_train) * 1.1
        if self.control_limit_kde > y_max:
            y_max = self.control_limit_kde * 1.1
            
        # 初始只绘制控制限
        ax.axhline(self.control_limit_kde, color='red', linestyle='--', 
                  label=f'控制限 ({self.control_limit_kde:.2f})', linewidth=1.5)
        
        # 为动态绘制创建空线条和散点
        # 计算最后20%数据的起始点
        total_points = len(self.I2_train)
        test_start = int(total_points * 0.8)
        
        # 创建两条线：训练集(蓝色)和测试集(橙色)
        line_train, = ax.plot([], [], label='I²统计量 (训练集)', color='blue', linewidth=1.5)
        line_test, = ax.plot([], [], label='I²统计量 (测试集)', color='orange', linewidth=1.5)
        scatter = ax.scatter([], [], color='red',
                         label='异常点', zorder=5, s=50)
        
        # 设置图形属性
        ax.set_ylim([y_min, y_max])
        ax.set_xlim([0, len(self.I2_train)])
        ax.set_title('I²统计量监控', fontsize=14)
        ax.set_xlabel('样本编号', fontsize=12)
        ax.set_ylabel('I²统计量', fontsize=12)
        ax.legend(fontsize=12, loc='upper left')
        ax.grid(True, linestyle='--', alpha=0.7)
        
        # 调整布局
        plt.tight_layout(pad=2.0)
        
        # 计算异常点的索引和值
        anomaly_indices = np.where(self.I2_train > self.control_limit_kde)[0]
        anomaly_values = self.I2_train[anomaly_indices]
        
        # 保存动态绘制需要的数据
        dynamic_chart_data = {
            'fig': fig,
            'ax': ax,
            'line_train': line_train,
            'line_test': line_test,
            'scatter': scatter,
            'I2_values': self.I2_train,
            'control_limit': self.control_limit_kde,
            'anomaly_indices': anomaly_indices,
            'anomaly_values': anomaly_values,
            'test_start': test_start
        }
        
        return dynamic_chart_data

    def diagnose_faults(self):
        """执行故障诊断并生成专业的诊断报告"""
        try:
            # 获取数据集的异常点信息
            anomalies = np.where(self.I2_train > self.control_limit_kde)[0]
            
            # 生成诊断报告标题
            diagnosis_report = "ICA独立成分分析故障诊断报告\n"
            diagnosis_report += "=" * 50 + "\n\n"
            
            # 1. 总体状态评估
            diagnosis_report += "一、总体状态评估\n"
            diagnosis_report += "-" * 40 + "\n"
            
            # 计算关键指标
            total_samples = len(self.I2_train)
            anomaly_rate = len(anomalies) / total_samples * 100
            max_I2 = np.max(self.I2_train)
            mean_I2 = np.mean(self.I2_train)
            std_I2 = np.std(self.I2_train)
            
            diagnosis_report += f"1. 样本总数: {total_samples}\n"
            diagnosis_report += f"2. 异常检出率: {anomaly_rate:.2f}%\n"
            diagnosis_report += f"3. I²统计量分析:\n"
            diagnosis_report += f"   - 最大值: {max_I2:.2f}\n"
            diagnosis_report += f"   - 平均值: {mean_I2:.2f}\n"
            diagnosis_report += f"   - 标准差: {std_I2:.2f}\n"
            diagnosis_report += f"   - 控制限: {self.control_limit_kde:.2f}\n\n"
            
            # 2. 异常点详细分析
            diagnosis_report += "二、异常点详细分析\n"
            diagnosis_report += "-" * 40 + "\n"
            
            if len(anomalies) > 0:
                diagnosis_report += f"共检测到 {len(anomalies)} 个异常点:\n"
                
                # 按照I²值大小排序异常点
                sorted_indices = sorted(anomalies, key=lambda x: self.I2_train[x], reverse=True)
                
                for rank, idx in enumerate(sorted_indices, 1):
                    diagnosis_report += f"\n异常点 #{rank} (样本序号: {idx + 1}):\n"
                    I2_value = self.I2_train[idx]
                    deviation = I2_value - self.control_limit_kde
                    deviation_sigma = deviation / std_I2
                    
                    diagnosis_report += f"* I²统计量: {I2_value:.2f}\n"
                    diagnosis_report += f"* 超出控制限: {deviation:.2f} ({deviation_sigma:.2f}σ)\n"
                    diagnosis_report += f"* 置信度: {(1 - 1/I2_value)*100:.1f}%\n"
                    
                    # 计算贡献度
                    S_anomaly = self.S_train[idx].reshape(1, -1)
                    contribution = (S_anomaly ** 2) @ (self.ica.components_ ** 2)
                    contribution = contribution.flatten()
                    
                    # 获取贡献度最大的变量
                    column_names = self.chinese_columns if self.chinese_columns is not None else self.train_data.columns
                    contributions = list(zip(column_names, contribution))
                    contributions.sort(key=lambda x: x[1], reverse=True)
                    
                    diagnosis_report += "* 主要贡献变量:\n"
                    for var_name, contrib in contributions[:3]:
                        contrib_percent = (contrib / np.sum(contribution)) * 100
                        diagnosis_report += f"   - {var_name}: {contrib:.2f} ({contrib_percent:.1f}%)\n"
            else:
                diagnosis_report += "当前监测周期内未检测到显著异常\n"
            
            # 3. 诊断结论与建议
            diagnosis_report += "\n三、诊断结论与建议\n"
            diagnosis_report += "-" * 40 + "\n"
            
            if len(anomalies) > 0:
                # 分析异常程度
                if anomaly_rate > 10:
                    severity = "严重"
                    recommendation = "建议立即进行系统检查和维护"
                elif anomaly_rate > 5:
                    severity = "中等"
                    recommendation = "建议关注系统状态，适时进行维护"
                else:
                    severity = "轻微"
                    recommendation = "建议持续监控，定期检查"
                
                diagnosis_report += f"1. 异常程度: {severity}\n"
                diagnosis_report += f"2. 主要影响变量: {contributions[0][0]}\n"
                diagnosis_report += "3. 建议措施:\n"
                diagnosis_report += f"   - {recommendation}\n"
                diagnosis_report += f"   - 重点检查 {', '.join([c[0] for c in contributions[:3]])} 相关的系统部件\n"
                diagnosis_report += "   - 建议增加异常变量的采样频率\n"
            else:
                diagnosis_report += "1. 系统运行状态正常\n"
                diagnosis_report += "2. 建议措施:\n"
                diagnosis_report += "   - 保持当前控制参数设置\n"
                diagnosis_report += "   - 继续执行常规维护计划\n"
            
            # 4. 技术说明
            diagnosis_report += "\n四、技术说明\n"
            diagnosis_report += "-" * 40 + "\n"
            diagnosis_report += "1. I²统计量超过控制限表示检测到异常\n"
            diagnosis_report += f"2. 控制限值 {self.control_limit_kde:.2f} 基于99%置信水平\n"
            diagnosis_report += "3. 贡献度分析基于ICA模型的独立成分\n"
            diagnosis_report += "4. 变量贡献度百分比表示其对异常的相对影响程度\n"
            
            return diagnosis_report
            
        except Exception as e:
            print(f"故障诊断失败: {str(e)}")
            import traceback
            traceback.print_exc()
            return None

def train_ica_model(data_df, n_components=None):
    """训练ICA模型并自动选择最佳变量数"""
    analyzer = ICAAnalyzer()
    
    try:
        # 1. 数据预处理
        processed_data = analyzer.preprocess_data(data_df)
        if processed_data is None:
            return None, []
            
        # 2. 标准化数据
        scaler = StandardScaler()
        X_scaled = scaler.fit_transform(processed_data)
        
        # 3. 如果指定了组件数量，直接使用
        if n_components is not None:
            figures = analyzer.main_function(data_df, n_components=n_components)
            if figures is None:
                return None, []
            return analyzer, figures
            
        # 4. 如果未指定组件数量，寻找最佳变量数
        min_error = float('inf')
        best_n = 2
        max_components = processed_data.shape[1]  # 使用所有特征作为最大变量数
        error_history = []
        
        for n in range(2, max_components + 1):
            try:
                ica = FastICA(n_components=n, random_state=42, max_iter=1000)
                ica.fit(X_scaled)
                error = get_reconstruction_error(ica, X_scaled)
                error_history.append((n, error))
                
                if error < min_error:
                    min_error = error
                    best_n = n
                    
            except Exception as e:
                print(f"变量数 {n} 训练失败: {str(e)}")
                continue
        
        # 5. 使用最佳变量数生成结果
        figures = analyzer.main_function(data_df, n_components=best_n)
        if figures is None:
            return None, []
            
        # 6. 添加误差曲线图
        if error_history:
            fig_error = plt.figure(figsize=(10, 6))
            components, errors = zip(*error_history)
            plt.plot(components, errors, 'b-o', label='重构误差')
            plt.scatter(best_n, min(errors), color='red', s=100, label=f'最佳变量数 ({best_n})')
            plt.xlabel('变量数', fontsize=12)
            plt.ylabel('重构误差', fontsize=12)
            plt.title('变量数与重构误差关系', fontsize=14)
            plt.grid(True, linestyle='--', alpha=0.7)
            plt.legend(fontsize=12)
            plt.tight_layout()
            figures.append(fig_error)
            
        return analyzer, figures
        
    except Exception as e:
        print(f"训练过程发生错误: {str(e)}")
        return None, []

class ModernButton(QPushButton):
    def __init__(self, text="", parent=None):
        super().__init__(text, parent)
        self.setFixedHeight(36)
        self.setCursor(Qt.CursorShape.PointingHandCursor)
        self.setStyleSheet("""
            QPushButton {
                background-color: #e6f7ff;
                color: #333333;
                border: 1px solid #91d5ff;
                border-radius: 4px;
                padding: 8px 16px;
                font-size: 14px;
            }
            QPushButton:hover {
                background-color: white;
                border-color: #1890ff;
                color: #1890ff;
            }
            QPushButton:pressed {
                background-color: #40a9ff;
                color: white;
                border-color: #1890ff;
            }
            QPushButton:disabled {
                background-color: #f5f5f5;
                color: #bfbfbf;
                border: 1px solid #d9d9d9;
            }
        """)

class CollapsibleSidebar(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedWidth(200)
        self.expanded = True
        
        # Main layout
        layout = QVBoxLayout(self)
        layout.setContentsMargins(10, 20, 10, 20)
        layout.setSpacing(10)
        
        # 添加标题或提示
        title = QLabel("ICA分析")
        title.setStyleSheet("""
            font-size: 18px;
            font-weight: bold;
            color: #1890ff;
            margin-bottom: 10px;
        """)
        title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(title)
        
        # 添加说明文字
        info = QLabel("使用左侧功能面板\n选择要执行的操作")
        info.setStyleSheet("""
            font-size: 12px;
            color: #666666;
            margin: 10px 0;
        """)
        info.setAlignment(Qt.AlignmentFlag.AlignCenter)
        info.setWordWrap(True)
        layout.addWidget(info)
        
        # 为兼容性保留按钮列表，但不显示在界面上
        self.buttons = []
        buttons_config = [
            ("训练模型", "train_model"),
            ("显示贡献图", "show_contribution"),
            ("故障诊断报告", "show_diagnosis")
        ]
        
        for text, func_name in buttons_config:
            btn = ModernButton(text)
            btn.setVisible(False)  # 设置按钮不可见
            self.buttons.append(btn)
        
        layout.addStretch()
        self.setStyleSheet("""
            QWidget {
                background-color: white;
                border-right: 1px solid #d9d9d9;
            }
        """)

class ContentPanel(QFrame):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setStyleSheet("""
            ContentPanel {
                background-color: white;
                border: none;
                margin: 10px;
            }
        """)
        
        # Create main layout
        self.main_layout = QVBoxLayout(self)
        self.main_layout.setContentsMargins(20, 20, 20, 20)
        self.main_layout.setSpacing(10)
        
        # 创建滚动区域
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidgetResizable(True)
        self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
        self.scroll_area.setStyleSheet("""
            QScrollArea {
                background-color: white;
                border: none;
            }
        """)
        self.main_layout.addWidget(self.scroll_area)
        self.scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        
        # Main content area
        self.content_area = QFrame()
        self.content_area.setStyleSheet("""
            QFrame {
                background-color: white;
                border: none;
            }
        """)
        # 将内容区域设置为滚动区域的部件
        self.scroll_area.setWidget(self.content_area)
        
        # Content area layout
        self.content_layout = QVBoxLayout(self.content_area)
        self.content_layout.setContentsMargins(20, 20, 20, 20)
        self.content_layout.setSpacing(10)
        
        # 存储当前显示的图形
        self.current_figure = None
        self.current_canvas = None

    def clear_figures(self):
        """清理当前显示的图形"""
        if self.current_canvas:
            self.current_canvas.deleteLater()
            self.current_canvas = None
        if self.current_figure:
            plt.close(self.current_figure)
            self.current_figure = None
        import gc
        gc.collect()

class ModernUI(QMainWindow):
    def __init__(self, main_window=None):
        super().__init__()
        self.main_window = main_window
        
        # 延迟初始化变量
        self.data_df = None
        self.analyzer = None
        self.figures = None
        self.file_path = None
        self.is_trained = False
        self.data_has_changed = False
        
        # 用于动态监控图的变量
        self.dynamic_chart_data = None
        self.animation_timer = None
        self.current_point_index = 0
        self.animation_speed = 5 # 动画速度（毫秒）
        self.animation_running = False
        self.canvas = None
        
        # 设置基本窗口属性
        self._setup_window()
        # 创建UI组件
        self._create_ui()
        
    def _setup_window(self):
        """设置窗口基本属性"""
        self.setWindowTitle("ICA 数据分析工具")
        self.setStyleSheet("QMainWindow {background-color: #f0f0f0;}")
        
        screen = QApplication.primaryScreen().geometry()
        self.resize(1100, 700)
        self.move(
            (screen.width() - 1100) // 2,
            (screen.height() - 700) // 2
        )
        manager.state_changed.connect(self.on_state_changed)
        self.setWindowState(manager.state)
        
    def _create_ui(self):
        """创建UI组件"""
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QHBoxLayout(main_widget)
        main_layout.setSpacing(0)
        main_layout.setContentsMargins(0, 0, 0, 0)
        
        # 创建但不显示侧边栏，只保留为了兼容性
        self.sidebar = CollapsibleSidebar()
        self.sidebar.setVisible(False)  # 隐藏侧边栏
        self.sidebar_layout = self.sidebar.layout() or QVBoxLayout(self.sidebar)
        
        # 保留按钮连接但不显示按钮
        self.sidebar.buttons[0].clicked.connect(self.show_train_dialog)
        self.sidebar.buttons[1].clicked.connect(self.show_contribution)
        self.sidebar.buttons[2].clicked.connect(self.show_diagnosis)
        
        # 只添加内容面板
        self.content = ContentPanel()
        main_layout.addWidget(self.content)
        
        # 添加动态绘制按钮
        self.animate_btn = ModernButton("动态绘制监控图")
        self.animate_btn.setCheckable(True)
        self.animate_btn.clicked.connect(self.toggle_animation)
        
        # 将按钮添加到侧边栏
        self.sidebar_layout.addWidget(self.animate_btn)
        
        # 创建并显示欢迎页面
        self._create_welcome_page()

    def _create_welcome_page(self):
        """创建欢迎页面"""
        welcome_widget = QWidget()
        welcome_layout = QVBoxLayout(welcome_widget)
        welcome_layout.setSpacing(20)
        welcome_layout.setContentsMargins(10, 10, 10, 30)  # 增加底部边距
        
        # 添加标题
        title_label = QLabel("ICA独立成分分析")
        title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        title_label.setStyleSheet("""
            font-size: 24px;
            font-weight: bold;
            color: #1890ff;
            margin-top: 20px;
        """)
        welcome_layout.addWidget(title_label)
        
        # 添加图标
        icon_label = QLabel()
        icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        # 使用内置图标
        icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_ComputerIcon)
        pixmap = icon.pixmap(128, 128)
        icon_label.setPixmap(pixmap)
        welcome_layout.addWidget(icon_label)
        
        # 添加描述
        desc_label = QLabel(
            "ICA（独立成分分析）是一种常用的信号处理技术，"
            "通过将混合信号分离成统计独立的成分，"
            "以捕获数据中的独立特征。"
        )
        desc_label.setWordWrap(True)
        desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        desc_label.setStyleSheet("""
            font-size: 14px;
            color: #666;
            margin: 10px 20px;
            line-height: 1.5;
        """)
        welcome_layout.addWidget(desc_label)
        
        # 添加功能说明（使用QFrame添加阴影效果）
        functions_frame = QFrame()
        functions_frame.setStyleSheet("""
            background-color: #f5f5f5;
            border-radius: 8px;
            padding: 10px;
        """)
        functions_layout = QVBoxLayout(functions_frame)
        
        functions_title = QLabel("主要功能：")
        functions_title.setStyleSheet("font-size: 16px; font-weight: bold; color: #333;")
        functions_layout.addWidget(functions_title)
        
        functions = [
            ("训练模型", "训练ICA模型，提取独立成分"),
            ("显示贡献图", "显示各变量对独立成分的贡献"),
            ("故障诊断报告", "生成详细的故障诊断分析报告")
        ]
        
        for title, desc in functions:
            function_label = QLabel(f"• <b>{title}</b>: {desc}")
            function_label.setStyleSheet("""
                font-size: 14px;
                color: #333;
                margin: 5px 0;
            """)
            functions_layout.addWidget(function_label)
        
        welcome_layout.addWidget(functions_frame)
        
        # 添加伸缩空间
        welcome_layout.addStretch()
        
        # 添加使用提示（无边框）
        tip_label = QLabel("请从左侧功能面板选择相应的操作按钮开始使用")
        tip_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        tip_label.setStyleSheet("""
            font-size: 14px;
            color: #1890ff;
            margin-top: 10px;
            margin-bottom: 10px;
            font-weight: bold;
        """)
        welcome_layout.addWidget(tip_label)
        
        # 添加底部空白区域，确保内容不被遮挡
        bottom_spacer = QLabel()
        bottom_spacer.setFixedHeight(30)
        welcome_layout.addWidget(bottom_spacer)
        
        # 将欢迎界面添加到内容区域
        self.content.content_layout.addWidget(welcome_widget)
        self.welcome_widget = welcome_widget

    def clear_content(self, preserve_welcome=False):
        """清除内容区域
        
        Args:
            preserve_welcome: 是否保留欢迎页面，如果为True且当前显示的是欢迎页面，则不清除
        """
        # 停止动画并清理动态图表资源
        if hasattr(self, 'animation_timer') and self.animation_timer:
            self.animation_timer.stop()
            self.animation_timer = None
        
        # 清理所有matplotlib图形
        plt.close('all')
        
        # 清理动态图表数据
        if hasattr(self, 'dynamic_chart_data') and self.dynamic_chart_data:
            self.dynamic_chart_data = None
        
        # 清理canvas
        if hasattr(self, 'canvas') and self.canvas:
            self.canvas.setVisible(False)
            self.canvas.setParent(None)
            self.canvas.deleteLater()
            self.canvas = None
            
        # 清理动态canvas
        if hasattr(self, 'canvas_dynamic') and self.canvas_dynamic:
            self.canvas_dynamic.setVisible(False)
            self.canvas_dynamic.setParent(None)
            self.canvas_dynamic.deleteLater()
            self.canvas_dynamic = None
            
        try:
            if hasattr(self, 'canvas_container') and self.canvas_container:
                self.canvas_container.deleteLater()
                self.canvas_container = None
        except RuntimeError:
            pass
        
        # 检查是否需要保留欢迎页面
        if preserve_welcome and hasattr(self, 'welcome_widget') and self.welcome_widget:
            # 如果内容区域只有一个子部件，且是欢迎页面，则不清除
            if self.content.content_layout.count() == 1:
                first_item = self.content.content_layout.itemAt(0)
                if first_item and first_item.widget() == self.welcome_widget:
                    # 只更新状态信息
                    self.update_status("请选择左侧功能进行操作", 'info')
                    return
            
        while self.content.content_layout.count():
            try:
                item = self.content.content_layout.takeAt(0)
                if item and item.widget():
                    item.widget().deleteLater()
            except RuntimeError:
                pass
        
        # 清除状态信息并重置为默认提示
        self.update_status("请选择左侧功能进行操作", 'info')

    def show_loading(self, show=True, message="处理中..."):
        if show:
            loading_label = QLabel(message)
            loading_label.setStyleSheet("""
                color: #1890ff;
                font-size: 16px;
                padding: 20px;
            """)
            self.content.content_layout.addWidget(loading_label)
            self.loading_label = loading_label
        else:
            if hasattr(self, 'loading_label'):
                self.loading_label.deleteLater()

    def update_status(self, message, type='info'):
        # 由于已经删除了status_label，这个方法现在只是一个空方法
        # 保留它是为了兼容性，避免其他地方调用它时出错
        pass

    def load_data(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "选择数据文件",
            "",
            "Excel files (*.xlsx);;CSV files (*.csv)"
        )
        
        if file_path:
            try:
                self.show_loading(True, "正在加载数据...")
                self.update_status("正在加载数据...", 'info')
                
                if file_path.endswith('.xlsx'):
                    new_data_df = pd.read_excel(file_path)
                else:
                    new_data_df = pd.read_csv(file_path, header=None)
                    new_data_df.columns = [f'X{i+1}' for i in range(new_data_df.shape[1])]
                    
                self.analyzer = ICAAnalyzer()
                processed_data = self.analyzer.preprocess_data(new_data_df)
                
                if processed_data is None:
                    error_msg = "数据预处理失败，可能的原因：\n" \
                              "1. 包含非数值型数据时会自动转换为数值\n" \
                              "2. 数据列数要求：至少需要2列有效数据，建议不超过200列（当前：%d列）\n" \
                              "3. 数据变异性不足（每列数据需要有足够的变化）\n" \
                              "4. 数据格式要求：\n" \
                              "   - 缺失值将使用该列的均值自动填充\n" \
                              "   - 每列数据应有合理的变化范围\n" \
                              "\n建议：\n" \
                              "- 检查每列的数据类型是否合适\n" \
                              "- 考虑减少特征数量，选择最重要的特征\n" \
                              "- 确保数据的质量和完整性" % new_data_df.shape[1]
                    self.update_status("数据预处理失败", 'error')
                    QMessageBox.critical(self, "预处理错误", error_msg)
                    return
                    
                self.data_df = processed_data
                self.file_path = file_path
                self.data_has_changed = True
                self.update_status("数据加载成功", 'success')
                
                # 自动跳转到训练界面
                QTimer.singleShot(500, self.show_train_dialog)
                
            except Exception as e:
                self.update_status(f"加载数据失败: {str(e)}", 'error')
                QMessageBox.critical(self, "错误", f"加载数据失败: {str(e)}")
            finally:
                self.show_loading(False)

    def show_train_dialog(self):
        if not hasattr(self, 'data_df') or self.data_df is None:
            if self.main_window is not None and hasattr(self.main_window, 'data_df') and self.main_window.data_df is not None:
                # 从主窗口获取数据
                self.data_df = self.main_window.data_df
                self.file_path = self.main_window.file_path
            else:
                QMessageBox.warning(self, "警告", "请先在主界面加载数据")
                return
            
        self.clear_content(preserve_welcome=False)
        train_panel = QWidget()  # 使用QWidget而不是QFrame，更灵活
        
        layout = QVBoxLayout(train_panel)
        layout.setSpacing(30)
        layout.setContentsMargins(40, 30, 40, 50)  # 增加底部边距
        
        # Title
        title = QLabel("模型训练配置")
        title.setStyleSheet("""
            color: #1890ff;
            font-size: 24px;
            font-weight: bold;
            padding: 10px;
            background-color: white;
            border-bottom: 2px solid #1890ff;
        """)
        title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        title.setFixedHeight(60)
        
        title_container = QHBoxLayout()
        title_container.addStretch(1)
        title_container.addWidget(title)
        title_container.addStretch(1)
        layout.addLayout(title_container)
        
        # Input panel
        input_panel = QFrame()
        input_panel.setStyleSheet("""
            QFrame {
                background-color: white;
                border: 1px solid #d9d9d9;
                border-radius: 4px;
                padding: 20px;
            }
        """)
        input_layout = QHBoxLayout(input_panel)
        input_layout.setContentsMargins(20, 15, 20, 15)
        
        label = QLabel("独立元数量:")
        label.setStyleSheet("""
            color: #333333;
            font-size: 16px;
        """)
        input_layout.addWidget(label)
        
        self.n_components_input = QLineEdit()
        self.n_components_input.setStyleSheet("""
            QLineEdit {
                background-color: white;
                color: #333333;
                border: 1px solid #d9d9d9;
                border-radius: 4px;
                padding: 8px;
                font-size: 16px;
            }
            QLineEdit:focus {
                border-color: #1890ff;
            }
        """)
        # 设置默认值为变量最大值
        self.n_components_input.setText(str(self.data_df.shape[1]))
        self.n_components_input.setFixedWidth(100)
        input_layout.addWidget(self.n_components_input)
        
        range_label = QLabel(f"(可选范围: 2 - {self.data_df.shape[1]})")
        range_label.setStyleSheet("""
            color: #1890ff;
            font-size: 14px;
            margin-left: 10px;
        """)
        input_layout.addWidget(range_label)
        input_layout.addStretch()
        
        input_container = QHBoxLayout()
        input_container.addStretch(1)
        input_container.addWidget(input_panel)
        input_container.addStretch(1)
        layout.addLayout(input_container)
        
        # Train button
        train_btn = ModernButton("开始训练")
        train_btn.setFixedWidth(200)
        train_btn.clicked.connect(self.train_model)
        
        button_container = QHBoxLayout()
        button_container.addStretch(1)
        button_container.addWidget(train_btn)
        button_container.addStretch(1)
        layout.addLayout(button_container)
        
        layout.addStretch()
        
        # 添加底部空白区域，确保内容不被遮挡
        bottom_spacer = QLabel()
        bottom_spacer.setFixedHeight(40)
        layout.addWidget(bottom_spacer)
        
        self.content.content_layout.addWidget(train_panel)

    def train_model(self):
        try:
            n_components = int(self.n_components_input.text())
            if n_components < 2 or n_components > self.data_df.shape[1]:
                QMessageBox.warning(
                    self,
                    "警告",
                    f"组件数量必须在2到{self.data_df.shape[1]}之间"
                )
                return
                
            self.show_loading(True, "正在训练模型...")
            self.update_status("正在训练模型...", 'info')
            
            # 初始化分析器
            self.analyzer = ICAAnalyzer()
            processed_data = self.analyzer.preprocess_data(self.data_df)
            
            if processed_data is None:
                self.update_status("数据预处理失败", 'error')
                error_msg = "数据预处理失败，请检查数据格式和内容"
                QMessageBox.critical(self, "预处理错误", error_msg)
                return
                
            # 设置训练数据
            self.analyzer.train_data = processed_data
            self.analyzer.columns = processed_data.columns
            self.analyzer.scaler = StandardScaler()
            self.analyzer.X_train_scaled = self.analyzer.scaler.fit_transform(processed_data)
            self.analyzer.ica = FastICA(n_components=n_components, random_state=42, max_iter=1000)
            self.analyzer.S_train = self.analyzer.ica.fit_transform(self.analyzer.X_train_scaled)
            self.analyzer.I2_train = np.sum(self.analyzer.S_train**2, axis=1)
            
            # 使用KDE计算控制限
            kde = gaussian_kde(self.analyzer.I2_train)
            I2_min, I2_max = self.analyzer.I2_train.min(), self.analyzer.I2_train.max()
            I2_grid = np.linspace(I2_min, I2_max, 1000)
            kde_values = kde.evaluate(I2_grid)
            cdf = np.cumsum(kde_values)
            cdf /= cdf[-1]
            self.analyzer.control_limit_kde = I2_grid[np.searchsorted(cdf, 0.99)]
            
            # 设置异常点
            self.analyzer.anomalies = self.analyzer.I2_train > self.analyzer.control_limit_kde
            
            if self.analyzer is None:
                self.update_status("模型训练失败", 'error')
                error_msg = "模型训练失败，请检查：\n\n"
                error_msg += "1. 数据中是否包含异常值\n"
                error_msg += "2. 数据变异性是否足够\n"
                error_msg += f"3. 独立分量数量是否合适（当前：{n_components}）\n"
                error_msg += "4. 数据是否已正确标准化"
                QMessageBox.critical(self, "训练错误", error_msg)
                return
                
            self.is_trained = True
            self.data_has_changed = False
            self.update_status("模型训练完成", 'success')
            
            # 显示训练结果（直接进入动态监控模式）
            self.show_training_result()
            
        except ValueError:
            self.update_status("请输入有效的整数", 'error')
            QMessageBox.warning(self, "警告", "请输入有效的整数")
        except Exception as e:
            self.update_status(f"训练失败: {str(e)}", 'error')
            QMessageBox.critical(self, "错误", f"训练失败: {str(e)}")
        finally:
            self.show_loading(False)

    def start_training(self):
        # 清理所有图形资源
        self.clear_figures()
        plt.close('all')
        
        # 清理动态图表数据
        self.dynamic_chart_data = None
        
        # 直接进入动态监控模式
        self.show_training_result()

    def start_animation(self):
        """直接开始动态绘制"""
        if hasattr(self, 'dynamic_chart_data') and self.dynamic_chart_data:
            # 清理原有图形资源
            if hasattr(self, 'animation_timer') and self.animation_timer:
                self.animation_timer.stop()
                self.animation_timer = None
            
            # 清除所有matplotlib图形资源
            plt.close('all')
            
            # 强制垃圾回收以释放资源
            import gc
            gc.collect()
            
            # 确保原有canvas被完全清理
            if hasattr(self, 'canvas') and self.canvas:
                self.canvas.setVisible(False)
                self.canvas.setParent(NNone)
                self.canvas.deleteLater()
                self.canvas = None
            
            # 确保内容区域的图形被清理
            self.content.clear_figures()
            
            # 切换到动态图表
            if hasattr(self, 'canvas_dynamic') and self.canvas_dynamic:
                # 确保动态图是最新的
                self.dynamic_chart_data['fig'].canvas.draw()
                self.canvas_dynamic.setVisible(True)
                
                # 重置动画状态
                self.current_point_index = 0
                self.animation_running = True
                
                # 启动定时器
                if not self.animation_timer:
                    self.animation_timer = QTimer()
                    self.animation_timer.timeout.connect(self.update_animation)
                    self.animation_timer.start(self.animation_speed)

    def show_training_result(self):
        self.clear_content(preserve_welcome=False)
        
        # 清除当前显示的图形
        if hasattr(self, 'canvas'):
            if self.canvas:
                self.canvas.setParent(None)
                self.canvas.deleteLater()
            self.canvas = None
        if hasattr(self, 'canvas_dynamic'):
            if self.canvas_dynamic:
                self.canvas_dynamic.setParent(None)
                self.canvas_dynamic.deleteLater()
            self.canvas_dynamic = None
            
        # 创建一个容器来包含所有内容
        container = QWidget()
        container_layout = QVBoxLayout(container)
        container_layout.setContentsMargins(10, 10, 10, 10)
        
        result_panel = QFrame()
        result_panel.setStyleSheet("""
            QFrame {
                background-color: white;
                border: none;
                padding: 20px;
            }
        """)
        result_layout = QVBoxLayout(result_panel)
        result_layout.setContentsMargins(0, 0, 0, 0)
        
        # 创建canvas容器
        canvas_container = QWidget()
        canvas_container.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        canvas_layout = QVBoxLayout(canvas_container)
        canvas_layout.setContentsMargins(0, 0, 0, 0)
        canvas_layout.setSpacing(0)
        
        # 创建动态监控图数据
        self.dynamic_chart_data = self.analyzer.create_dynamic_monitoring_chart()
        if self.dynamic_chart_data:
            # 调整图例位置和图形布局
            fig = self.dynamic_chart_data['fig']
            ax = self.dynamic_chart_data['ax']
            
            # 调整图形大小
            fig.set_size_inches(12, 7)
            
            # 移动图例到图形上方
            ax.legend(bbox_to_anchor=(0.5, 1.15), loc='center', ncol=3)
            
            # 调整图形布局以适应图例
            fig.subplots_adjust(top=0.85, bottom=0.15)
            
            self.canvas_dynamic = FigureCanvas(self.dynamic_chart_data['fig'])
            self.canvas_dynamic.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
            canvas_layout.addWidget(self.canvas_dynamic)
            self.canvas_dynamic.setVisible(True)
        
        result_layout.addWidget(canvas_container, 1)
        container_layout.addWidget(result_panel)
        
        # 添加底部空白区域，确保内容不被遮挡
        bottom_spacer = QLabel()
        bottom_spacer.setFixedHeight(40)
        container_layout.addWidget(bottom_spacer)
        
        self.content.content_layout.addWidget(container)
        
        # 启动动态图表展示
        QTimer.singleShot(500, self.start_animation)

    def toggle_animation(self):
        """切换动态绘制的开始/停止状态"""
        if not self.dynamic_chart_data:
            QMessageBox.warning(self, "警告", "无法创建动态监控图")
            self.animate_btn.setChecked(False)
            return
            
        if self.animation_running:
            # 停止动画
            if self.animation_timer:
                self.animation_timer.stop()
                self.animation_timer = None
            self.animation_running = False
            self.animate_btn.setText("动态绘制监控图")
            
            # 保留最后一帧图表不清理
            if hasattr(self, 'canvas_dynamic') and self.canvas_dynamic:
                self.canvas_dynamic.setVisible(True)
            
            # 重置动画状态
            self.current_point_index = 0
            
        else:
            # 开始动画
            self.animation_running = True
            self.animate_btn.setText("停止动态绘制")
            
            # 不再清理所有图形资源
            if hasattr(self, 'canvas') and self.canvas:
                self.canvas.setVisible(False)
            
            # 确保动态图表可见
            if hasattr(self, 'canvas_dynamic') and self.canvas_dynamic:
                self.canvas_dynamic.setVisible(True)
                
            # 启动定时器
            if not self.animation_timer:
                self.animation_timer = QTimer()
                self.animation_timer.timeout.connect(self.update_animation)
                self.animation_timer.start(self.animation_speed)
    
    def set_animation_speed(self, speed_ms):
        """设置动画速度"""
        self.animation_speed = speed_ms
        if self.animation_timer and self.animation_running:
            self.animation_timer.stop()
            self.animation_timer.start(self.animation_speed)
    
    def update_animation(self):
        """更新动态绘制的帧"""
        if not self.dynamic_chart_data or self.current_point_index >= len(self.dynamic_chart_data['I2_values']):
            self.toggle_animation()  # 动画完成后停止
            return
            
        # 获取当前需要绘制的点
        n_points = self.current_point_index + 1
        x_data = np.arange(n_points)
        y_data = self.dynamic_chart_data['I2_values'][:n_points]
        # 获取测试集起始点
        test_start = self.dynamic_chart_data.get('test_start', len(y_data))
        
        # 更新训练集和测试集线图数据
        if n_points <= test_start:
            # 只绘制训练集
            self.dynamic_chart_data['line_train'].set_data(x_data, y_data)
            self.dynamic_chart_data['line_test'].set_data([], [])
        else:
            # 绘制训练集和测试集
            self.dynamic_chart_data['line_train'].set_data(
                x_data[:test_start], y_data[:test_start])
            self.dynamic_chart_data['line_test'].set_data(
                x_data[test_start:], y_data[test_start:])
        
        # 查找截至目前的异常点
        anomaly_mask = y_data > self.dynamic_chart_data['control_limit']
        anomaly_indices = np.where(anomaly_mask)[0]
        anomaly_values = y_data[anomaly_mask]
        anomaly_values = y_data[anomaly_mask]
        
        if len(anomaly_indices) > 0:
            # 更新散点图数据
            self.dynamic_chart_data['scatter'].set_offsets(np.column_stack([anomaly_indices, anomaly_values]))
            # 修改标题以显示异常点数量
            self.dynamic_chart_data['ax'].set_title(f'I²统计量监控（发现{len(anomaly_indices)}个异常点）', fontsize=14)
        else:
            # 清空散点图
            self.dynamic_chart_data['scatter'].set_offsets(np.zeros((0, 2)))
            self.dynamic_chart_data['ax'].set_title('I²统计量监控', fontsize=14)
        
        # 确保canvas_dynamic可见且是最新的
        if hasattr(self, 'canvas_dynamic') and self.canvas_dynamic:
            if not self.canvas_dynamic.isVisible():
                self.canvas_dynamic.setVisible(True)
                
            # 确保其他canvas不可见
            if hasattr(self, 'canvas') and self.canvas and self.canvas.isVisible():
                self.canvas.setVisible(False)
        
        # 重绘图表
        self.dynamic_chart_data['fig'].canvas.draw()
        self.dynamic_chart_data['fig'].canvas.flush_events()
        
        # 增加索引
        self.current_point_index += 1

    def show_contribution(self):
        if not self.is_trained:
            QMessageBox.warning(self, "警告", "请先训练模型")
            return
            
        try:
            self.clear_content(preserve_welcome=False)
            
            anomaly_indices = np.where(self.analyzer.anomalies)[0]
            if len(anomaly_indices) == 0:
                QMessageBox.information(self, "提示", "未检测到异常点")
                return

            # 创建控制面板
            control_panel = QFrame()
            control_panel.setStyleSheet("""
                QFrame {
                    background-color: white;
                    border: none;
                    padding: 5px;
                }
            """)
            
            control_layout = QHBoxLayout(control_panel)
            control_layout.setContentsMargins(10, 5, 10, 5)
            control_layout.setSpacing(10)
            
            self.prev_page_button = ModernButton("上一页")
            self.next_page_button = ModernButton("下一页")
            page_info = QLabel("第1页")
            page_info.setStyleSheet("font-size: 12px; color: #333333;")
            
            control_layout.addWidget(self.prev_page_button)
            control_layout.addWidget(page_info)
            control_layout.addWidget(self.next_page_button)
            
            self.content.content_layout.addWidget(control_panel)

            # 创建图表容器
            charts_container = QWidget()  # 使用QWidget而不是QFrame
            charts_layout = QGridLayout(charts_container)
            charts_layout.setSpacing(20)
            
            # 计算每页显示的图表数量（2行3列）
            self.charts_per_page = 6
            self.current_page = 0
            self.total_pages = (len(anomaly_indices) + self.charts_per_page - 1) // self.charts_per_page
            
            # 更新按钮状态和页面信息
            def update_page_info():
                page_info.setText(f"第{self.current_page + 1}页，共{self.total_pages}页")
                self.prev_page_button.setEnabled(self.current_page > 0)
                self.next_page_button.setEnabled(self.current_page < self.total_pages - 1)

            # 显示当前页的图表
            def show_current_page():
                # 清除现有图表
                for i in reversed(range(charts_layout.count())): 
                    charts_layout.itemAt(i).widget().deleteLater()
                
                # 计算当前页的索引范围
                start_idx = self.current_page * self.charts_per_page
                end_idx = min(start_idx + self.charts_per_page, len(anomaly_indices))
                
                # 使用更小的图表大小以适应固定容器
                chart_width = 350  
                chart_height = 250
                
                for i, idx in enumerate(range(start_idx, end_idx)):
                    anomaly_idx = anomaly_indices[idx]
                    fig = self.analyzer.plot_contribution_for_anomalies([anomaly_idx])[0]
                    
                    # 调整图表大小和布局
                    fig.set_size_inches(7, 4)  # 减小图表尺寸
                    
                    # 调整图表样式
                    ax = fig.axes[0]
                    ax.tick_params(axis='x', rotation=45, labelsize=7, pad=3)
                    ax.tick_params(axis='y', labelsize=8)
                    
                    # 设置y轴为整数格式
                    ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: int(x)))
                    
                    # 调整边距（平衡顶部和底部空间）
                    fig.subplots_adjust(
                        left=0.12,    # 左边距
                        right=0.88,   # 右边距
                        bottom=0.25,  # 减小底部空间
                        top=0.85,     # 减小顶部边距
                        hspace=0.3,   # 减小垂直间距（0.4→0.3）
                        wspace=0.2    # 减小水平间距（0.3→0.2）
                    )
                    
                    # 调整标题位置
                    ax.set_title(ax.get_title(), fontsize=9, pad=8)  # 减小字体和pad值
                    
                    # 调整y轴标签位置
                    ax.yaxis.set_label_coords(-0.18, 0.5)  # 进一步向左移动
                    
                    # 移除重复的边框设置（已在下文设置）
                    # 保留边框但移除右侧复选框
                    ax.spines['right'].set_visible(False)
                    ax.spines['top'].set_visible(False)
                    
                    canvas = FigureCanvas(fig)
                    canvas.setFixedSize(chart_width, chart_height)
                    
                    row = i // 3
                    col = i % 3
                    charts_layout.addWidget(canvas, row, col)
                
                update_page_info()

            # 连接按钮事件
            def next_page():
                if self.current_page < self.total_pages - 1:
                    self.current_page += 1
                    show_current_page()

            def prev_page():
                if self.current_page > 0:
                    self.current_page -= 1
                    show_current_page()

            self.prev_page_button.clicked.connect(prev_page)
            self.next_page_button.clicked.connect(next_page)
            
            # 设置网格布局的边距
            charts_layout.setContentsMargins(5, 5, 5, 5)  # 减小边距
            
            # 创建滚动区域
            scroll_area = QScrollArea()
            scroll_area.setWidgetResizable(True)
            scroll_area.setWidget(charts_container)
            scroll_area.setStyleSheet("""
                QScrollArea {
                    border: none;
                    background-color: transparent;
                }
            """)
            
            # 设置滚动区域的大小策略和最大高度
            scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
            scroll_area.setMaximumHeight(600)  # 限制最大高度
            
            # 设置图表容器的大小策略
            charts_container.setSizePolicy(QSizePolicy.Policy.Expanding, 
                                        QSizePolicy.Policy.Expanding)
            
            # 添加图表容器到主布局
            self.content.content_layout.addWidget(scroll_area)
            
            # 添加底部空白区域，确保内容不被遮挡
            bottom_spacer = QLabel()
            bottom_spacer.setFixedHeight(40)
            self.content.content_layout.addWidget(bottom_spacer)
            
            # 显示第一页
            show_current_page()
            
        except Exception as e:
            self.update_status(f"显示贡献图失败: {str(e)}", 'error')
            QMessageBox.critical(self, "错误", f"显示贡献图失败: {str(e)}")

    def show_diagnosis(self):
        if not self.is_trained:
            QMessageBox.warning(self, "警告", "请先训练模型")
            return
            
        try:
            self.clear_content(preserve_welcome=False)
            report = self.analyzer.diagnose_faults()
            
            # 创建一个容器来包含所有内容
            container = QWidget()
            container_layout = QVBoxLayout(container)
            container_layout.setContentsMargins(10, 10, 10, 10)
            
            text_display = QFrame()
            text_display.setStyleSheet("""
                QFrame {
                    background-color: white;
                    border: none;
                    padding: 20px;
                }
            """)
            layout = QVBoxLayout(text_display)
            
            text_edit = QTextEdit()
            text_edit.setStyleSheet("""
                QTextEdit {
                    background-color: white;
                    color: #333333;
                    border: none;
                    font-size: 14px;
                    line-height: 1.5;
                }
            """)
            text_edit.setPlainText(report)
            text_edit.setReadOnly(True)
            layout.addWidget(text_edit)
            
            container_layout.addWidget(text_display)
            
            # 添加底部空白区域，确保内容不被遮挡
            bottom_spacer = QLabel()
            bottom_spacer.setFixedHeight(40)
            container_layout.addWidget(bottom_spacer)
            
            self.content.content_layout.addWidget(container)
            self.update_status("故障诊断报告已生成", 'success')
            
        except Exception as e:
            self.update_status(f"生成诊断报告失败: {str(e)}", 'error')
            QMessageBox.critical(self, "错误", f"生成诊断报告失败: {str(e)}")

    def on_state_changed(self, new_state):
        """响应全局窗口状态改变"""
        if self.windowState() != new_state:
            self.setWindowState(new_state)

    def changeEvent(self, event):
        if event.type() == event.Type.WindowStateChange:
            # 更新全局状态
            manager.state = self.windowState()
        super().changeEvent(event)

    def reset_to_welcome(self):
        """重置界面到欢迎页状态"""
        # 清除当前内容
        self.clear_content(preserve_welcome=False)
        
        # 重新创建欢迎页面
        welcome_widget = QWidget()
        welcome_layout = QVBoxLayout(welcome_widget)
        welcome_layout.setSpacing(20)
        welcome_layout.setContentsMargins(10, 10, 10, 30)  # 增加底部边距
        
        # 添加标题
        title_label = QLabel("ICA独立成分分析")
        title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        title_label.setStyleSheet("""
            font-size: 24px;
            font-weight: bold;
            color: #1890ff;
            margin-top: 20px;
        """)
        welcome_layout.addWidget(title_label)
        
        # 添加图标
        icon_label = QLabel()
        icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        # 使用内置图标
        icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_ComputerIcon)
        pixmap = icon.pixmap(128, 128)
        icon_label.setPixmap(pixmap)
        welcome_layout.addWidget(icon_label)
        
        # 添加描述
        desc_label = QLabel(
            "ICA（独立成分分析）是一种常用的信号处理技术，"
            "通过将混合信号分离成统计独立的成分，"
            "以捕获数据中的独立特征。"
        )
        desc_label.setWordWrap(True)
        desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        desc_label.setStyleSheet("""
            font-size: 14px;
            color: #666;
            margin: 10px 20px;
            line-height: 1.5;
        """)
        welcome_layout.addWidget(desc_label)
        
        # 添加功能说明（使用QFrame添加阴影效果）
        functions_frame = QFrame()
        functions_frame.setStyleSheet("""
            background-color: #f5f5f5;
            border-radius: 8px;
            padding: 10px;
        """)
        functions_layout = QVBoxLayout(functions_frame)
        
        functions_title = QLabel("主要功能：")
        functions_title.setStyleSheet("font-size: 16px; font-weight: bold; color: #333;")
        functions_layout.addWidget(functions_title)
        
        functions = [
            ("训练模型", "训练ICA模型，提取独立成分"),
            ("显示贡献图", "显示各变量对独立成分的贡献"),
            ("故障诊断报告", "生成详细的故障诊断分析报告")
        ]
        
        for title, desc in functions:
            function_label = QLabel(f"• <b>{title}</b>: {desc}")
            function_label.setStyleSheet("""
                font-size: 14px;
                color: #333;
                margin: 5px 0;
            """)
            functions_layout.addWidget(function_label)
        
        welcome_layout.addWidget(functions_frame)
        
        # 添加伸缩空间
        welcome_layout.addStretch()
        
        # 添加使用提示（无边框）
        tip_label = QLabel("请从左侧功能面板选择相应的操作按钮开始使用")
        tip_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        tip_label.setStyleSheet("""
            font-size: 14px;
            color: #1890ff;
            margin-top: 10px;
            margin-bottom: 10px;
            font-weight: bold;
        """)
        welcome_layout.addWidget(tip_label)
        
        # 添加底部空白区域，确保内容不被遮挡
        bottom_spacer = QLabel()
        bottom_spacer.setFixedHeight(30)
        welcome_layout.addWidget(bottom_spacer)
        
        # 将欢迎界面添加到内容区域
        self.content.content_layout.addWidget(welcome_widget)
        self.welcome_widget = welcome_widget
        
        # 更新状态
        self.update_status("请选择左侧功能进行操作", 'info')

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = ModernUI()
    window.show()
    sys.exit(app.exec())